/*--------------------------------------------------------------------
 * ps_status.c
 *
 * 支持改变PostgreSQL后端ps显示的例程，以包含一些有用的信息。机制在各平台之间差异很大。
 *
 * src/backend/utils/misc/ps_status.c
 *
 * Copyright (c) 2000-2022, PostgreSQL Global Development Group
 * various details abducted from various places
 *--------------------------------------------------------------------
 */

#include "postgres.h"

#include <unistd.h>
#ifdef HAVE_SYS_PSTAT_H
#include <sys/pstat.h>			/* for HP-UX */
#endif
#ifdef HAVE_PS_STRINGS
#include <machine/vmparam.h>	/* for old BSD */
#include <sys/exec.h>
#endif
#if defined(__darwin__)
#include <crt_externs.h>
#endif

#include "libpq/libpq.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "utils/guc.h"
#include "utils/ps_status.h"

extern char **environ;
bool		update_process_title = true;


/*
 * 更新 ps 显示的替代方法：
 *
 * PS_USE_SETPROCTITLE_FAST
 *	   使用函数 setproctitle_fast(const char *, ...)
 *	   （更新版本的 FreeBSD 系统）
 * PS_USE_SETPROCTITLE
 *	   使用函数 setproctitle(const char *, ...)
 *	   （更新版本的 BSD 系统）
 * PS_USE_PSTAT
 *	   使用 pstat(PSTAT_SETCMD, )
 *	   （HPUX）
 * PS_USE_PS_STRINGS
 *	   分配 PS_STRINGS->ps_argvstr = "string"
 *	   （某些 BSD 系统）
 * PS_USE_CHANGE_ARGV
 *	   分配 argv[0] = "string"
 *	   （某些其他 BSD 系统）
 * PS_USE_CLOBBER_ARGV
 *	   写入 argv 和环境区域
 *	   （Linux 和大多数 SysV 风格的系统）
 * PS_USE_WIN32
 *	   将字符串推送为 Windows 事件的名称
 * PS_USE_NONE
 *	   不更新 ps 显示
 *	   （这是默认选项，因为最安全。）
 */
#if defined(HAVE_SETPROCTITLE_FAST)
#define PS_USE_SETPROCTITLE_FAST
#elif defined(HAVE_SETPROCTITLE)
#define PS_USE_SETPROCTITLE
#elif defined(HAVE_PSTAT) && defined(PSTAT_SETCMD)
#define PS_USE_PSTAT
#elif defined(HAVE_PS_STRINGS)
#define PS_USE_PS_STRINGS
#elif (defined(BSD) || defined(__hurd__)) && !defined(__darwin__)
#define PS_USE_CHANGE_ARGV
#elif defined(__linux__) || defined(_AIX) || defined(__sgi) || (defined(sun) && !defined(BSD)) || defined(__svr5__) || defined(__darwin__)
#define PS_USE_CLOBBER_ARGV
#elif defined(WIN32)
#define PS_USE_WIN32
#else
#define PS_USE_NONE
#endif


/* 不同系统希望缓冲区以不同方式填充 */
#if defined(_AIX) || defined(__linux__) || defined(__darwin__)
#define PS_PADDING '\0'
#else
#define PS_PADDING ' '
#endif


#ifndef PS_USE_NONE

#ifndef PS_USE_CLOBBER_ARGV
/* 除一个选项外，所有选项都需要一个缓冲区以写入其 ps 行 */
#define PS_BUFFER_SIZE 256
static char ps_buffer[PS_BUFFER_SIZE];
static const size_t ps_buffer_size = PS_BUFFER_SIZE;
#else							/* PS_USE_CLOBBER_ARGV */
static char *ps_buffer;			/* 将指向 argv 区域 */
static size_t ps_buffer_size;	/* 空间在运行时确定 */
static size_t last_status_len;	/* 用于最小化覆盖长度 */
#endif							/* PS_USE_CLOBBER_ARGV */

static size_t ps_buffer_cur_len;	/* 名义上的 strlen(ps_buffer) */

static size_t ps_buffer_fixed_size; /* 常量前缀的大小 */

#endif							/* 不是 PS_USE_NONE */

/* 保存原始 argv[] 位置 */
static int	save_argc;
static char **save_argv;


/*
 * 在启动早期调用此函数以保存原始 argc/argv 值。
 * 如果需要，我们会复制原始 argv[] 数组，以保护它
 * 不被后续 ps_display 操作覆盖。
 *
 * （原始 argv[] 不会被此例程覆盖，但可能在 init_ps_display 中被
 * 覆盖。此外，环境字符串的物理位置可能会被移动，因此应在任何
 * 可能尝试保留 getenv() 结果的代码之前调用此函数。但请参见
 * 其中的 musl hack。）
 *
 * 请注意，在失败的情况下，这无法调用 elog()，因为还未初始化。
 * 我们依赖于 write_stderr() 代替。
 */
char	  **
save_ps_display_args(int fc_argc, char **fc_argv)
{
	save_argc = fc_argc;
	save_argv = fc_argv;

#if defined(PS_USE_CLOBBER_ARGV)

	/*
	 * 如果我们要覆盖 argv 区域，计算可用空间。
	 * 并且移动环境字符串以腾出额外空间。
	 */
	{
		char	   *fc_end_of_area = NULL;
		char	  **fc_new_environ;
		int			fc_i;

		/*
		 * 检查连续的 argv 字符串
		 */
		for (fc_i = 0; fc_i < fc_argc; fc_i++)
		{
			if (fc_i == 0 || fc_end_of_area + 1 == fc_argv[fc_i])
				fc_end_of_area = fc_argv[fc_i] + strlen(fc_argv[fc_i]);
		}

		if (fc_end_of_area == NULL)	/* 可能无法发生？ */
		{
			ps_buffer = NULL;
			ps_buffer_size = 0;
			return fc_argv;
		}

		/*
		 * 检查在 argv 后面的连续 environ 字符串
		 */
		for (fc_i = 0; environ[fc_i] != NULL; fc_i++)
		{
			if (fc_end_of_area + 1 == environ[fc_i])
			{
				/*
				 * musl 动态链接器保持对
				 * LD_LIBRARY_PATH 初始值的静态指针，如果该值在
				 * 进程的环境中定义。因此，我们绝不能覆盖该设置的值，
				 * 因此不能将 end_of_area 向前推进。Musl 不定义任何识别的
				 * 编译器符号，因此除非我们看到一个识别我们知道是安全的
				 * Linux libc 的符号，否则我们必须这么做。
				 */
#if defined(__linux__) && (!defined(__GLIBC__) && !defined(__UCLIBC__))
				if (strncmp(environ[i], "LD_LIBRARY_PATH=", 16) == 0)
				{
					/*
					 * 我们可以覆盖名称，但在等号处停止。
					 * 未来的循环迭代将找不到任何更多
					 * 连续空间，但我们不会提前中断，因为我们
					 * 需要计算 environ[] 条目的总数。
					 */
					fc_end_of_area = environ[i] + 15;
				}
				else
#endif
				{
					fc_end_of_area = environ[fc_i] + strlen(environ[fc_i]);
				}
			}
		}

		ps_buffer = fc_argv[0];
		last_status_len = ps_buffer_size = fc_end_of_area - fc_argv[0];

		/*
		 * 将环境移开
		 */
		fc_new_environ = (char **) malloc((fc_i + 1) * sizeof(char *));
		if (!fc_new_environ)
		{
			write_stderr("out of memory\n");
			exit(1);
		}
		for (fc_i = 0; environ[fc_i] != NULL; fc_i++)
		{
			fc_new_environ[fc_i] = strdup(environ[fc_i]);
			if (!fc_new_environ[fc_i])
			{
				write_stderr("out of memory\n");
				exit(1);
			}
		}
		fc_new_environ[fc_i] = NULL;
		environ = fc_new_environ;
	}
#endif							/* PS_USE_CLOBBER_ARGV */

#if defined(PS_USE_CHANGE_ARGV) || defined(PS_USE_CLOBBER_ARGV)

	/*
	 * 如果我们要改变原始 argv[]，则为参数解析的目的制作一个副本。
	 *
	 * 注意：不要想着去掉对 argv[] 的复制，即使
	 * postmaster.c 在我们考虑
	 * 改变 ps 显示之前很早就结束了对 argv[] 的查看。在某些平台上，getopt() 会保留对
	 * argv 数组的指针，如果在此期间 argv 存储被破坏，再次调用它来分析子进程的参数字符串
	 * 时将会变得非常困惑。其他平台对 argv[] 有其他依赖。
	 */
	{
		char	  **fc_new_argv;
		int			fc_i;

		fc_new_argv = (char **) malloc((fc_argc + 1) * sizeof(char *));
		if (!fc_new_argv)
		{
			write_stderr("out of memory\n");
			exit(1);
		}
		for (fc_i = 0; fc_i < fc_argc; fc_i++)
		{
			fc_new_argv[fc_i] = strdup(fc_argv[fc_i]);
			if (!fc_new_argv[fc_i])
			{
				write_stderr("out of memory\n");
				exit(1);
			}
		}
		fc_new_argv[fc_argc] = NULL;

#if defined(__darwin__)

		/*
		 * macOS（以及其他 NeXT 衍生平台？）有一个静态的 argv 指针副本，
		 * 我们可以这样修复：
		 */
		*_NSGetArgv() = new_argv;
#endif

		fc_argv = fc_new_argv;
	}
#endif							/* PS_USE_CHANGE_ARGV 或 PS_USE_CLOBBER_ARGV */

	return fc_argv;
}

#ifdef FDD
//用于在 PostmasterMain 中将 "-K" 后的密钥设置为空，避免泄露
void fdb_hideSensitiveArg(const char *fc_paramstr, int fc_paramcnt)
{
	int		fc_i = 0, fc_len = 0;

	for(; fc_i<save_argc; fc_i++)
	{
		if(strcmp(save_argv[fc_i], fc_paramstr) == 0)
		{
			for(fc_i++; fc_i < save_argc && fc_paramcnt > 0; fc_i++)
			{
				fc_len = strlen(save_argv[fc_i]);
				memset(save_argv[fc_i], 0, fc_len);
				fc_paramcnt--;
			}
			break;
		}
	}
}
#endif //FDD
/*
 * 在子进程启动期间调用一次以设置标识
 * 值。
 *
 * 如果 fixed_part 为 NULL，将从 MyBackendType 获取默认值。
 *
 * 在此时，原始 argv[] 数组可能会被覆盖。
 */
void init_ps_display(const char *fc_fixed_part)
{
#ifndef PS_USE_NONE
	bool		fc_save_update_process_title;
#endif

	Assert(fc_fixed_part || MyBackendType);
	if (!fc_fixed_part)
		fc_fixed_part = GetBackendTypeDesc(MyBackendType);

#ifndef PS_USE_NONE
	/* 独立后端没有 ps 显示 */
	if (!IsUnderPostmaster)
		return;

	/* 如果你没有调用 save_ps_display_args()，则没有 ps 显示 */
	if (!save_argv)
		return;

#ifdef PS_USE_CLOBBER_ARGV
	/* 如果 ps_buffer 是一个指针，则它可能仍然为 null */
	if (!ps_buffer)
		return;
#endif

	/*
	 * 如果需要，覆盖 argv[] 指向适当的空间
	 */

#ifdef PS_USE_CHANGE_ARGV
	save_argv[0] = ps_buffer;
	save_argv[1] = NULL;
#endif							/* PS_USE_CHANGE_ARGV */

#ifdef PS_USE_CLOBBER_ARGV
	{
		int			fc_i;

		/* 使额外的 argv 插槽指向 end_of_area（一个 NUL） */
		for (fc_i = 1; fc_i < save_argc; fc_i++)
			save_argv[fc_i] = ps_buffer + ps_buffer_size;
	}
#endif							/* PS_USE_CLOBBER_ARGV */

	/*
	 * 制作 ps 显示的固定前缀。
	 */

#if defined(PS_USE_SETPROCTITLE) || defined(PS_USE_SETPROCTITLE_FAST)

	/*
	 * 显然 setproctitle() 已经在 ps
	 * 行中添加了 `progname:` 前缀
	 */
#define PROGRAM_NAME_PREFIX ""
#elif defined(FDD)
#define PROGRAM_NAME_PREFIX "fbase: "
#else
#define PROGRAM_NAME_PREFIX "postgres: "
#endif

	if (*cluster_name == '\0')
	{
		snprintf(ps_buffer, ps_buffer_size,
				 PROGRAM_NAME_PREFIX "%s ",
				 fc_fixed_part);
	}
	else
	{
		snprintf(ps_buffer, ps_buffer_size,
				 PROGRAM_NAME_PREFIX "%s: %s ",
				 cluster_name, fc_fixed_part);
	}

	ps_buffer_cur_len = ps_buffer_fixed_size = strlen(ps_buffer);

	/*
	 * 在第一次运行时，强制更新。
	 */
	fc_save_update_process_title = update_process_title;
	update_process_title = true;
	set_ps_display("");
	update_process_title = fc_save_update_process_title;
#endif							/* 不是 PS_USE_NONE */
}



/*
 * 调用此函数以将 ps 状态显示更新为固定前缀以及
 * 通过参数传递的你当前正在做的事情的指示。
 */
void set_ps_display(const char *fc_activity)
{
#ifndef PS_USE_NONE
	/* update_process_title=off 禁用更新 */
	if (!update_process_title)
		return;

	/* 独立后端没有 ps 显示 */
	if (!IsUnderPostmaster)
		return;

#ifdef PS_USE_CLOBBER_ARGV
	/* 如果 ps_buffer 是一个指针，则它可能仍然为 null */
	if (!ps_buffer)
		return;
#endif

	/* 更新 ps_buffer 以包含固定部分和活动 */
	strlcpy(ps_buffer + ps_buffer_fixed_size, fc_activity,
			ps_buffer_size - ps_buffer_fixed_size);
	ps_buffer_cur_len = strlen(ps_buffer);

	/* 如有必要，将新设置传输到内核 */

#ifdef PS_USE_SETPROCTITLE
	setproctitle("%s", ps_buffer);
#elif defined(PS_USE_SETPROCTITLE_FAST)
	setproctitle_fast("%s", ps_buffer);
#endif

#ifdef PS_USE_PSTAT
	{
		union pstun fc_pst;

		fc_pst.pst_command = ps_buffer;
		pstat(PSTAT_SETCMD, fc_pst, ps_buffer_cur_len, 0, 0);
	}
#endif							/* PS_USE_PSTAT */

#ifdef PS_USE_PS_STRINGS
	PS_STRINGS->ps_nargvstr = 1;
	PS_STRINGS->ps_argvstr = ps_buffer;
#endif							/* PS_USE_PS_STRINGS */

#ifdef PS_USE_CLOBBER_ARGV
	/* 填充未使用的内存；只需破坏旧状态字符串的剩余部分 */
	if (last_status_len > ps_buffer_cur_len)
		MemSet(ps_buffer + ps_buffer_cur_len, PS_PADDING,
			   last_status_len - ps_buffer_cur_len);
	last_status_len = ps_buffer_cur_len;
#endif							/* PS_USE_CLOBBER_ARGV */

#ifdef PS_USE_WIN32
	{
		/*
		 * Win32 不支持显示任何更改的参数。为了使
		 * 跟踪哪个后端在做什么成为可能，我们创建了一个
		 * 命名对象，可以使用例如进程资源管理器查看。
		 */
		static HANDLE ident_handle = INVALID_HANDLE_VALUE;
		char		name[PS_BUFFER_SIZE + 32];

		if (ident_handle != INVALID_HANDLE_VALUE)
			CloseHandle(ident_handle);

		sprintf(name, "pgident(%d): %s", MyProcPid, ps_buffer);

		ident_handle = CreateEvent(NULL, TRUE, FALSE, name);
	}
#endif							/* PS_USE_WIN32 */
#endif							/* 不是 PS_USE_NONE */
}


/*
 * 返回当前在 ps 显示中的内容，以防有人需要
 * 它。请注意，仅返回活动部分。在某些平台上
 * 字符串将不会以 null 结尾，因此返回有效
 * 长度到 *displen。
 */
const char * get_ps_display(int *fc_displen)
{
#ifdef PS_USE_CLOBBER_ARGV
	/* 如果 ps_buffer 是一个指针，则它可能仍然为 null */
	if (!ps_buffer)
	{
		*fc_displen = 0;
		return "";
	}
#endif

#ifndef PS_USE_NONE
	*fc_displen = (int) (ps_buffer_cur_len - ps_buffer_fixed_size);

	return ps_buffer + ps_buffer_fixed_size;
#else
	*fc_displen = 0;
	return "";
#endif
}
